/**
  ETFDAQ Project
  \class TAmADC32
  \brief a 32 ECL ch ADC Mesytec card
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-09-05
  \date 2024-09-05 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include "TAmADC32.h"
#include "TAMsg.h"
#include "TAYaml.h"
#include "mesy_reg.h"

using std::cout;
using std::endl;

#define er TAMsg::Error
#define wn TAMsg::Warn

const double TAmADC32::kInputRange[3] = {4, 10, 8}; ///< input range
const int TAmADC32::kResN[5] = {2048, 4096, 4096, 8192, 8192}; ///< ch width
///< conversion time in us
const double TAmADC32::kResConvT[5] = {0.8, 1.6, 3.2, 6.4, 12.5};

TAmADC32::TAmADC32(TAController *ctrl, const TAYaml &c) : TAMesytec(ctrl, c),
fResN(2048), fInputRange(4), fResConvT(0.8), fUseGate(false), fGateDelay{0},
fGateWidth{0}{
  fEventCounter = 0;
  fCardPurpose = ECardPurpose::ADC;
} // end ctor

TAmADC32::~TAmADC32(){}

// config the card
void TAmADC32::Initialize(){
  TAMesytec::Initialize();
  if(fIRQLevel) ConfigIRQ(fIRQLevel, fIRQVector, fEventTrigger, true);

  // default are both false, while register default is false (ud) and true (ov)
  KeepUnderflow(fIsKeepUnderflow);
  KeepOverflow(fIsKeepOverflow);
  
  // multi-event buffer
  // 0: single-event transfer; 1: unlimited multi-event transfer
  // 2: read max_transfer_data words, then emit berr
  // 0b1011 (0xb): max_transfer_data is in events
  RegWriteA32D16(mesyreg::multi_event, 0b1);
  // mesyreg::max_transfer_data only works for mesyreg::multi_event=0xb
  // RegWriteA32D16(mesyreg::max_transfer_data, fEventTrigger);

  fDataLenFormat = 4; // 0-1-2-3: D8-16-32-MBLT64-nEv for VME read
  if(fDataLenFormat < 2 || fDataLenFormat > 4)
    er("TAmADC32", "Initialize: abnormal data_len_format: %d", fDataLenFormat);
  RegWriteA32D16(mesyreg::data_len_format, fDataLenFormat);
  // store time stamp (ts) instead of event counter in event trailer
  RegWriteA32D16(mesyreg::marking_type, 0b11); // 00: ev ctr; 01: ts; 11: extdd ts

  const auto &c = fConfig; // just for convenience
  // select the gates as the trigger
  if(c["useGate"]){
    fUseGate = c["useGate"].as<bool>();
    if(fUseGate){
      if(fBankOp) RegWriteA32D16(mesyreg::use_gg, 0b11);
      else RegWriteA32D16(mesyreg::use_gg, 0b01);
    } // end if
  } // end if
  // configure the gates
  if(c["gateDelay"]){
    fGateDelay[0] = c["gateDelay"][0].as<uint16_t>();
    fGateDelay[1] = c["gateDelay"][1].as<uint16_t>();
    RegWriteA32D16(mesyreg::hold_delay0, fGateDelay[0]);
    RegWriteA32D16(mesyreg::hold_delay1, fGateDelay[1]);
  } // end if
  if(c["gateWidth"]){
    fGateWidth[0] = c["gateWidth"][0].as<uint16_t>();
    fGateWidth[1] = c["gateWidth"][1].as<uint16_t>();
    RegWriteA32D16(mesyreg::hold_width0, fGateWidth[0]);
    RegWriteA32D16(mesyreg::hold_width1, fGateWidth[1]);
  } // end if
  if(c["inputRange"]) SetInputRange(c["inputRange"].as<uint16_t>());

  fInputRange = kInputRange[RegReadA32D16(mesyreg::input_range)];
  fResN = kResN[fRes];
  fResConvT = kResConvT[fRes];
  fBlip = 1000. * fInputRange / fResN; // in mV

  // set nim busy output function
  // 0: busy (default); 1-2: gate0-1 out; 3: cbus 4: buffer full
  // 8: data over buffer thre; 9: ev over buffer thre
  if(c && c["NIM_busy_function"])
    RegWriteA32D16(mesyreg::NIM_busy, c["NIM_busy_function"].as<uint16_t>());
} // end member function Initialize

// only the first 8 bits in thre are valid by default
void TAmADC32::SetThreshold(int ch, uint16_t thre) const{
  if(ch < 0 || ch > 31) er("TAmADC32", "SetThreshold:"
      "%s in slot %d, illegal ch id: %d", fCardName.data(), fSlot, ch);
  RegWriteA32D16(mesyreg::thresholds + ch * fThreAddrIncre, thre & 0x1fff);
} // end member function SetThreshold

// 0-1-2: 4-10-8V
void TAmADC32::SetInputRange(uint16_t r){
  if(r < 0 || r > 2) er("TAmADC32", "SetInputRange: illegal range %s", r);
  RegWriteA32D16(mesyreg::input_range, r);
} // end member function SetInputRange

// [0-1-2-3-4]: [2,4,4h,8,8h]k
void TAmADC32::SetResolution(uint16_t res){
  if(res > 4) er("TAmADC32", "SetResolution: illegal resolution: %d", res);
  RegWriteA32D16(mesyreg::resolution, res);
} // end member function SetResolution

// default: underflow values are ignored (not stored)
void TAmADC32::KeepUnderflow(bool opt) const{
  if(opt) RegWriteA32D16(mesyreg::ignore_thresholds, 0b1);
  else RegWriteA32D16(mesyreg::ignore_thresholds, 0b0);
} // end member function KeepUnderflow

// default: overflow values are keeped (stored)
void TAmADC32::KeepOverflow(bool opt) const{
  if(opt) RegWriteA32D16(mesyreg::skip_overrange, 0b0);
  else RegWriteA32D16(mesyreg::skip_overrange, 0b1);
} // end member function KeepOverflow

// print config info
void TAmADC32::Print() const{
  TAMesytec::Print();
  int delay[2]{}, width[2]{};
  for(int i = 0; i < 2; i++){
    if(0 == fGateDelay[i]) delay[i] = 25;
    else if(1 == fGateDelay[i]) delay[i] = 150;
    else delay[i] = 100 + fGateDelay[i]*50;

    if(0 == fGateWidth[i]) width[i] = 0;
    else width[i] = 50. + fGateWidth[i]*50;
  } // end for
  if(fUseGate){
    printf("gate delay: %d, %d ns\n", delay[0], delay[1]);
    printf("gate width: %d, %d ns\n", width[0], width[1]);
  } // end if

  printf("Input Range: 0~%f V, LSB: %f mV\n", fInputRange, fBlip);
  printf("conversion time: %f us, dynamic range: %u ch\n", fResConvT, fResN);
  fflush(stdout);
} // end member function Print

void TAmADC32::PrintEvent(const uint32_t *ev){
  printf("\n\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  cout << "\033[32;1m-------- Event Print -------- \033[0m" << endl;
  
  // the header has been MODIFIED to be compatible with CAEN decoding codes
  // and distinguishable from V1x90 cards
  // see TAMesytec::ModifyHeader(uint32_t &h) for details
  const uint32_t header = ev[0];
  const int nw = (header>>8 & 0xFFF); // nof following words
  const int res = header>>20 & 0x7;
  int sig = header>>24 & 0x3;
  const int slot = header>>27 & 0x1F;

  int extddts = 0; // extended time stamp
  static const char *pos[] = {"data", "header", "UDF", "trailer", "err",
    "err", "err", "err", "err", "err", "err"};
  printf("%s: slot: %d, ev_len: %d, res: %d\n", pos[sig], slot, nw + 1, res);
  for(int i = 1; i < nw; i++){
    const uint32_t data = ev[i];
    const int sig = data>>30 & 0x3;
    const int fix = data>>21 & 0x1FF;
    const int chid = data>>16 & 0x1F;
    const bool ov = data>>14 & 0x1; // overflow
    const int adc = data & 0x3FFF;
    if(!sig){
      if(!fix) printf("dummy word\n");
      else if(0b100100 == fix){
        extddts = data & 0xFFFF;
        printf("extended time stamp: %d\n", extddts);
      } // end if
      else if(0b100000 == fix)
        printf("%s, chid: %02d, ov: %d, adc: %d\n", pos[sig], chid, ov, adc);
      else wn("TAmADC32", "0. PrintEvent: abnormal fix");
    } // end if
    else{
      printf("abnormal event word, i: %d\n", i);
      printf("%s, chid: %02d, ov: %d, adc: %d\n", pos[sig], chid, ov, adc);
      er("TAmADC32", "PrintEvent: abnormal event word encountered");
    } // end else
  } // end for over i
  const uint32_t trailer = ev[nw];
  sig = trailer>>30 & 0x3;
  if(3 != sig)
    wn("TAmADC32", "PrintEvent: Not a trailer word encountered, esig: %d", sig);
  else{
    const int ts = trailer & 0x3FFFFFFF;
    const double times = (ts + extddts*pow(2,30)) * 6.25e-8; // in seconds 62.5=1/16MHz
    printf("%s, time stamp: %u, extended time stamp: %u\n",
      pos[trailer>>30 & 0x3], ts, extddts);
    printf("time stamp in seconds: %.6f s (using 16MHz VME CLK)\n", times);
    fflush(stdout);
  } // end else
  cout << "\033[32;1m------- Event Print BYE ------- \033[0m" << endl;
} // end member function PrintEvent
